﻿using iTool.ClusterComponent;
using iTool.Common;
using iTool.PluginCore.Server;
using iTool.Utils;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace iTool.Core
{
    /// <summary>
    /// 插件管理器
    /// </summary>
    public class ManagerPluginCoreServer
    {
        TaskCompletionSource<Task> TaskSource = new TaskCompletionSource<Task>();
        Dictionary<string, ServerPluginSchema> PluginsMap = new Dictionary<string, ServerPluginSchema>();
        public IConfiguration Configuration { get; }
        public iObserverbalFileChange iObserverbal { get; }
        

        public ManagerPluginCoreServer(IConfiguration configuration)
        {
            Configuration = configuration;
            PluginsMap = new Dictionary<string, ServerPluginSchema>();

            iPrint.Line("\tiTool> Load plugins starting...", ConsoleColor.Cyan);
            // 加载所有Type
            var baseAssemblies = iAssemblyExtension.LoadDirectory("basePlugins", AppContext.BaseDirectory, "iToolPlugin.");
            var pluginsExtension = iAssemblyExtension.LoadDirectory("plugins", Path.Combine(AppContext.BaseDirectory, "Plugins"), "iToolPlugin.");
            var assemblies = baseAssemblies.Concat(pluginsExtension).Distinct();

            // 创建Map对象
            var types = this.GetPluginsByInterface<ILServerPlugin>(assemblies.ToArray());
            this.AddPluginInstancesOfTypes(types);

            iPrint.Line($"\tiTool> Create plugins count:{PluginsMap.Count}.", ConsoleColor.Cyan);
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            iPrint.Line("\tiTool> Load plugins success!", ConsoleColor.Cyan);
            Console.ForegroundColor = ConsoleColor.White;

            this.BuildeLocalCallFile();

            TaskSource.TrySetResult(Task.CompletedTask);

            // 监听插件目录的变化
            this.iObserverbal = new iObserverbalFileChange(Path.Combine(AppContext.BaseDirectory, "Plugins"), "*.dll", true, false);
            this.iObserverbal.OnChange += (e,s) => 
            {
                Console.WriteLine("Plugin OnChange, start reload...");
                TaskSource = new TaskCompletionSource<Task>();

                try
                {
                    var baseAssemblies = iAssemblyExtension.LoadDirectory("basePlugins", AppContext.BaseDirectory, "iToolPlugin.");
                    //var pluginsExtension = iAssemblyExtension.LoadDirectory("plugins", "./Plugins", "iToolPlugin.");
                    var pluginsExtension = iAssemblyExtension.LoadDirectory("plugins", Path.Combine(AppContext.BaseDirectory, "Plugins"));
                    var assemblies = baseAssemblies.Concat(pluginsExtension).Distinct();

                    var types = this.GetPluginsByInterface<ILServerPlugin>(assemblies.ToArray());
                    PluginsMap = new Dictionary<string, ServerPluginSchema>();
                    this.AddPluginInstancesOfTypes(types);
                    this.BuildeLocalCallFile();
                    Console.WriteLine("Plugin OnChange, start reload success.");
                }
                catch (Exception ex)
                {
                    iPrint.Error(ex.Message);
                }
                finally
                {
                    TaskSource.TrySetResult(Task.CompletedTask);
                }

            };
        }

        
        private void AddPluginInstancesOfTypes(IEnumerable<Type> types)
        {
            foreach (var type in types)
            {
                var value = new ServerPluginSchema();

                {
                    Type[] constractorParameterTypes = new Type[0];
                    object[] parametes = new object[0];
                    ParameterInfo[] constractorParameter = type.GetConstructors().Single().GetParameters();
                    if (constractorParameter.Count() > 0)
                    {
                        constractorParameterTypes = constractorParameter.Select(a => a.ParameterType).ToArray();
                        parametes = new object[constractorParameterTypes.Length];
                        for (int i = 0; i < constractorParameterTypes.Count(); i++)
                        {
                            parametes[i] = constractorParameterTypes[i] == typeof(IConfiguration) ? this.Configuration : iBox.GetService(constractorParameterTypes[i], "PluginsProvider");
                        }
                    }

                    //  创建的实例
                    value.Instance = Activator.CreateInstance(type, parametes);
                }

                // 获取所有导出的方法
                MethodInfo[] methods = type.GetMethods().Where(t => t.GetCustomAttribute<ExportMethodAttribute>() != null).ToArray();

                // 执行任何方法
                foreach (var methodInfo in methods)
                {
                    ExportMethodAttribute exportMethodAttribute = methodInfo.GetCustomAttribute<ExportMethodAttribute>();

                    var methodEntity = new Method();
                    methodEntity.methodInfo = methodInfo;
                    methodEntity.ParametersTypes = methodInfo.GetParameters().Select(a => a.ParameterType).ToArray();
                    methodEntity.IsAsync = methodInfo.IsDefined(typeof(AsyncStateMachineAttribute), false);
                    methodEntity.IsTaskDynamic = methodInfo.ReturnType.Namespace == "System.Threading.Tasks" && methodInfo.ReturnType.Name.StartsWith("Task`");
                    value.ExportMethods.Add(exportMethodAttribute.MethodName ?? methodInfo.Name, methodEntity);
                }

                var interfaceName = ((ILServerPlugin)value.Instance).Name;

                if (!PluginsMap.ContainsKey(interfaceName))
                { 
                    var descriptionAttribute = type.GetCustomAttribute<DescriptionAttribute>();

                    if (descriptionAttribute != null && !descriptionAttribute.IsDefaultAttribute())
                    {
                        value.Decription = descriptionAttribute.Description;
                    }

                    // 键入插件集合
                    PluginsMap.Add(interfaceName, value);
                }
                else
                {
#if DEBUG
                    throw new Exception(string.Format("Plugins {0} Already exists", interfaceName));
#endif
                    Console.WriteLine("Plugins {0} Already exists", interfaceName);
                }

                    
            }
        }

        /// <summary>
        /// 执行方法
        /// </summary>
        /// <param name="pluginName">插件名称</param>
        /// <param name="methodName">导出方法名称</param>
        /// <param name="parameters">参数</param>
        /// <returns></returns>
        public async Task<dynamic> InvokeAsync(string pluginName, string exportMethodName, params object[] parameters)
        {
            // 有可能正在加载插件
            await TaskSource.Task;

            if (PluginsMap.ContainsKey(pluginName))
            {
                var plugin = PluginsMap[pluginName];

                return await plugin.InvokeAsync(exportMethodName, parameters);
            }

            throw new Exception($"未知 methodName : {pluginName}");
        }

        private Type[] GetPluginsByInterface<T>(Assembly[] assemblies)
            where T : class
        {
            return assemblies.SelectMany(a => a.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(T))))
                .ToArray();
        }

        private void BuildeLocalCallFile()
        {
            StringBuilder stringBuilder = new StringBuilder();
            StringBuilder editExtralibBuilder = new StringBuilder();

            stringBuilder.AppendLine("const axios = require('axios');");
            stringBuilder.AppendLine("var server = 'http://localhost:17010';");

            stringBuilder.AppendLine("function _getPluginParameters(pluginName,exportMethodName, parameters) {");
            stringBuilder.AppendLine("\treturn {\"pluginName\": pluginName,\"exportMethodName\": exportMethodName,\"parameters\":parameters};");
            stringBuilder.AppendLine("}");

            stringBuilder.AppendLine("module.exports = {");

            foreach (var item in PluginsMap)
            {

                // editor description
                if (!string.IsNullOrWhiteSpace(item.Value.Decription))
                {
                    stringBuilder.AppendLine($"/**");
                    stringBuilder.AppendLine($" * {item.Value.Decription}");
                    stringBuilder.AppendLine($" */");
                }
                editExtralibBuilder.AppendLine($"declare class {item.Key} {{");


                // run fn
                if (!string.IsNullOrWhiteSpace(item.Value.Decription))
                {
                    stringBuilder.AppendLine($"\t/**");
                    stringBuilder.AppendLine($"\t * {item.Value.Decription}");
                    stringBuilder.AppendLine($"\t */");
                    item.Value.Decription = string.Empty;
                }

                stringBuilder.AppendLine($"\t{item.Key}:{{");

                foreach (var method in item.Value.ExportMethods)
                {
                    var descriptionAttribute = method.Value.methodInfo.GetCustomAttribute<DescriptionAttribute>();
                    // editor description
                    editExtralibBuilder.AppendLine($"\t/**");
                    if (descriptionAttribute != null && !descriptionAttribute.IsDefaultAttribute())
                        editExtralibBuilder.AppendLine($"\t * {descriptionAttribute.Description}");

                    foreach (var parameter in method.Value.methodInfo.GetParameters())
                        editExtralibBuilder.AppendLine($"\t * @param {{{parameter.ParameterType.Name}}} {parameter.Name}");

                    editExtralibBuilder.AppendLine($"\t */");

                    string resultTypeName = null;
                    if (method.Value.methodInfo.ReturnType?.Name.Contains("Task") == true)
                    {
                        resultTypeName = string.Join(',', method.Value.methodInfo.ReturnType?.GenericTypeArguments.Select(item => $"Task<{item.Name}>"));
                    }
                    else if (!string.IsNullOrWhiteSpace(method.Value.methodInfo.ReturnType?.Name))
                    {
                        if (method.Value.methodInfo.ReturnType?.GenericTypeArguments?.Length > 0)
                        {
                            resultTypeName = method.Value.methodInfo.ReturnType.Name.Split('`')[0] + $"<{string.Join(',', method.Value.methodInfo.ReturnType?.GenericTypeArguments.Select(item => $"{item.Name}"))}>";
                        }
                        else if (method.Value.methodInfo.ReturnType?.Name.Contains("`") == true)
                        {
                            resultTypeName = method.Value.methodInfo.ReturnType.Name.Split('`')[0];
                        }
                        else
                        {
                            resultTypeName = method.Value.methodInfo.ReturnType?.Name;
                        }
                    }

                    editExtralibBuilder.AppendLine($"\t static {method.Key}({string.Join(',', method.Value.methodInfo.GetParameters().Select(parameter => $" {parameter.Name}:{parameter.ParameterType.Name}"))}): {resultTypeName ?? "Any"}");

                    // run fn
                    stringBuilder.AppendLine($"\t\t/**");
                    if (descriptionAttribute != null && !descriptionAttribute.IsDefaultAttribute())
                        stringBuilder.AppendLine($"\t\t * {descriptionAttribute.Description}");

                    foreach (var parameter in method.Value.methodInfo.GetParameters())
                        stringBuilder.AppendLine($"\t\t * @param {{{parameter.ParameterType.Name}}} {parameter.Name}");

                    stringBuilder.AppendLine($"\t\t */");

                    stringBuilder.AppendLine($"\t\t{method.Key}: async function() {{");
                    stringBuilder.AppendLine("\t\t\tvar parameters = [];if (arguments) {for(var i in arguments){parameters.push(arguments[i])}};");
                    stringBuilder.AppendLine($"\t\t\tvar result = await axios.post(server, _getPluginParameters(\"{item.Key}\",\"{method.Key}\", parameters));return result.data;");
                    stringBuilder.AppendLine("\t\t},");
                }

                // editor description
                editExtralibBuilder.AppendLine("}");
                // run fn
                stringBuilder.AppendLine("\t},");
            }

            stringBuilder.AppendLine("}");

            File.WriteAllText("./pluginsInfo.js", stringBuilder.ToString());
            File.WriteAllText("./editExtralib-PluginsInfo.js", editExtralibBuilder.ToString());
            stringBuilder = null;
        }


    }


    public class ServerPluginSchema
    {
        /// <summary>
        /// 创建的实例
        /// </summary>
        public object Instance { get; set; }
        /// <summary>
        /// 说明
        /// </summary>
        public string Decription { get; set; }
        /// <summary>
        /// 导出的方法集合
        /// </summary>
        public Dictionary<string, Method> ExportMethods { get; set; } = new Dictionary<string, Method>();

        public async Task<dynamic> InvokeAsync(string methodName, params object[] input)
        {
            if (ExportMethods.ContainsKey(methodName))
            {
                var method = ExportMethods[methodName];

                var parameters = new object[method.ParametersTypes.Count()];

                for (int i = 0; i < method.ParametersTypes.Count(); i++)
                {
                    if (input.Length <= i)
                    {
                        break;
                    }

                    if (method.ParametersTypes[i] == typeof(string))
                    {
                        parameters[i] = input[i];
                    }
                    else
                    {
                        parameters[i] = JsonConvert.DeserializeObject(input[i].ToString(), method.ParametersTypes[i]);
                    }
                }

                if (method.IsAsync || method.IsTaskDynamic)
                {
                    //Type? returnType = method.methodInfo.ReturnType;

                    //var xxx1 = Convert.ChangeType("", method.methodInfo.ReturnType);

                    return await (dynamic)method.methodInfo.Invoke(this.Instance, parameters);

                    //return await (Task<dynamic>)method.methodInfo.Invoke(this.Instance, parameters);
                }
                else
                {
                    return (dynamic)method.methodInfo.Invoke(this.Instance, parameters);
                }
            }

            throw new Exception($"未知 methodName : {methodName}");
        }
    }

    public class Method
    {
        /// <summary>
        ///  方法对象
        /// </summary>
        public MethodInfo methodInfo { get; set; }

        /// <summary>
        /// 是否是异步
        /// </summary>
        public bool IsAsync { get; set; }

        /// <summary>
        /// 是否是Task
        /// </summary>
        public bool IsTaskDynamic { get; set; }

        /// <summary>
        /// 参数类型集合
        /// </summary>
        public Type[] ParametersTypes { get; set; }
    }


    public class PluginRequestBodyParameter
    {
        public string PluginName { get; set; }
        public string ExportMethodName { get; set; }
        public object[] Parameters { get; set; }
    }
}
